﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Timers;
using System.Threading;
using System.Collections;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;
using System.Windows.Forms;

namespace CommLink
{
    /// <summary>
    /// Communication media managment object
    /// </summary>
    public class CommLink
    {
        /// <summary>
        /// Abstract communication object used to write/read data regardless of media.
        /// </summary>
        CommLinkAbstract commObject;
        /// <summary>
        /// Communication display log
        /// </summary>
        CommLog commLog;

        /// <summary>
        /// Flag used to signal messaging thread termination.
        /// </summary>
        Boolean threadTerminate = false;

        /// <summary>
        /// Gets the default (if any) connection type.
        /// </summary>
        /// <returns>Default connection type or null if not set</returns>
        public static object GetDefaultConnectionType()
        {
            try
            {
                return (CommLinkType)Enum.Parse(typeof(CommLinkType), Properties.Settings.Default.Default_communication_type);
            }
            catch
            {
                return null;
            }
        }
        /// <summary>
        /// Supportec communication medaia types
        /// </summary>
        public enum CommLinkType
        {
            /// <summary>
            /// Creates a dummy comm link object incapable of transmitting and receiving data.
            /// </summary>
            No_Communication,
            /// <summary>
            /// TCP/IP communication type
            /// </summary>
            TCP_Channel,
            /// <summary>
            /// UART through serial port
            /// </summary>
            CommPort_UART,
            /*
            /// <summary>
            /// CodeComposer Studio integrated messaging dll - dirrect acces (not functional!)
            /// </summary>
            CommLink_CCS_dll,
            /// <summary>
            /// Windows named pipes communication (with the fore mentioned CCS integrated dll)
            /// </summary>
            CommLink_Win_named_pipe,
            /// <summary>
            /// Saves outband messages to file and reads inbound messages from file (testing only)
            /// </summary>
            Dummy_File_IO,
             */
        }

        /// <summary>
        /// Message sending function callback prototype delegate
        /// </summary>
        /// <param name="msgStream">Bytestream array data to be sent</param>
        public delegate void MessageSend(byte[] msgStream);
        delegate void EmptyDeletgate();

        /// <summary>
        /// Public constructor.
        /// </summary>
        /// <param name="commType">Selected communication media</param>
        public CommLink(CommLinkType commType)
        {
            switch (commType)
            {
                /*
                case CommLinkType.CommLink_CCS_dll:
                    commObject = new CCSIntegratedDLLWrapper();
                    break;
                case CommLinkType.CommLink_Win_named_pipe:
                    commObject = new NamedPipesClientComm();
                    break;
                case CommLinkType.Dummy_File_IO:
                    commObject = new CommLinkDummyFileIO();
                    break;
                */
                case CommLinkType.CommPort_UART:
                    commObject = new SerialPortComm();
                    break;
                case CommLinkType.TCP_Channel:
                    commObject = new TCPIPComm();
                    break;
                default:
                    commObject = new CommLinkNoCommDummy();
                    break;
            }

            commObject.OnMessageReceived += new CommLinkAbstract.MessageReceived(commObj_OnMessageReceived);
            commObject.OnConnectionStatusChange += new CommLinkAbstract.ConnectionStatusChange(ccsDll_OnConnectionStatusChange);
        }

        /// <summary>
        /// Opens the underlying connection media
        /// </summary>
        public void establishConnection()
        {
            commObject.openConnection();

            Thread threadMessageSending = new Thread(new ThreadStart(threadMessageSendingFunction));
            threadMessageSending.IsBackground = true;
            threadMessageSending.Start();

            Thread threadMessageReceiving = new Thread(new ThreadStart(threadMessageReceivingFunction));
            threadMessageReceiving.IsBackground = true;
            threadMessageReceiving.Start();
        }

        /// <summary>
        /// Gets the connection type name
        /// </summary>
        /// <returns>Connection type name</returns>
        public string connectionTypeName()
        {
            return commObject.getName();
        }

        #region Event logging
        /// <summary>
        /// Enables and displays the communication logging
        /// </summary>
        public void displayCommLog()
        {
            commLog = new CommLog();
            commLog.Show();
        }
        /// <summary>
        /// Disables and hides comminication logging
        /// </summary>
        public void hideCommLog()
        {
            lock (this)
            {
                if (commLog != null)
                {
                    if (commLog.InvokeRequired)
                    {
                        commLog.Invoke(new EmptyDeletgate(hideCommLog));
                    }
                    else
                    {
                        commLog.Close();
                        commLog = null;
                    }
                }
            }
        }
        /// <summary>
        /// Posts a message to the error tab of the comm log window
        /// </summary>
        /// <param name="error">Text message to post</param>
        public void errorMessage(string error)
        {
            if (commLog != null)
            {
                try
                {
                    lock (commLog)
                    {
                        commLog.ErrorLineEntry(error);
                    }
                }
                catch
                {
                    commLog = null;
                }
            }
        }
        #endregion

        /// <summary>
        /// Destructor stopping the messaging threads.
        /// </summary>
        ~CommLink()
        {
            threadTerminate = true;
            msgToSend.Release();
            msgReceived.Release();
        }

        #region Message sending
        /// <summary>
        /// Semaphore for message sending thread.
        /// </summary>
        Semaphore msgToSend = new Semaphore(0, 1000);

        /// <summary>
        /// Queue containing messages to be sent.
        /// </summary>
        Queue<byte[]> sendingMsgDataList = new Queue<byte[]>();

        /// <summary>
        /// Message sending thread.
        /// </summary>
        void threadMessageSendingFunction()
        {
            while (!threadTerminate)
            {
                msgToSend.WaitOne();

                if (sendingMsgDataList.Count > 0 && !threadTerminate)
                {
                    byte[] refMsgData = sendingMsgDataList.Dequeue();

                    commObject.messageSend(refMsgData);
                }
            }
        }

        /// <summary>
        /// Sending a message data byte array.
        /// </summary>
        /// <param name="msgData">Message data byte array</param>
        public void msgSendData(byte[] msgData)
        {
            sendingMsgDataList.Enqueue(msgData); 
            msgToSend.Release();
            if (commLog != null)
            {
                try
                {
                    lock (commLog)
                    {
                        commLog.LogEntry(">> ");
                        commLog.LogLineEntry(msgData);
                    }
                }
                catch
                {
                    commLog = null;
                }
            }
        }
        #endregion

        #region Message receiving
        /// <summary>
        /// Semaphore for message receiving thread.
        /// </summary>
        Semaphore msgReceived = new Semaphore(0, 1000);

        /// <summary>
        /// Queue containing received messages.
        /// </summary>
        Queue<byte[]> receivedMsgDataList = new Queue<byte[]>();

        /// <summary>
        /// Message receiving thread.
        /// </summary>
        void threadMessageReceivingFunction()
        {
            while (!threadTerminate)
            {
                msgReceived.WaitOne();

                if (receivedMsgDataList.Count > 0 && !threadTerminate)
                {
                    byte[] refMsgData = receivedMsgDataList.Dequeue();

                    if (OnMessageReceived != null)
                    {
                        OnMessageReceived(refMsgData);
                    }
                    if (commLog != null)
                    {
                        try
                        {
                            lock (commLog)
                            {
                                commLog.LogEntry("<< ");
                                commLog.LogLineEntry(refMsgData);
                            }
                        }
                        catch
                        {
                            commLog = null;
                        }
                    }
                }
            }
        }

        /// <summary>
        /// OnMessageReceived CCS dll event handler
        /// </summary>
        /// <param name="msgData"></param>
        void commObj_OnMessageReceived(byte[] msgData)
        {
            receivedMsgDataList.Enqueue(msgData);
            msgReceived.Release();
        }   

        /// <summary>
        /// Delegate for message processing functions for OnMessageReceived event.
        /// </summary>
        /// <param name="msgData">Message data byte array</param>
        public delegate void MessageReceived(byte[] msgData);

        /// <summary>
        /// Event fired when a valid message is received.
        /// </summary>
        public event MessageReceived OnMessageReceived;
        #endregion

        #region Connection state events
        /// <summary>
        /// Connection status event forwarding
        /// </summary>
        /// <param name="state">Connection state</param>
        void ccsDll_OnConnectionStatusChange(CommLinkAbstract.ConnectionStates state)
        {
            if (OnConnectionStatusEvent != null)
                OnConnectionStatusEvent(state);
        }

        /// <summary>
        /// Delegate for connection status update events.
        /// </summary>
        /// <param name="eState">Connection status</param>
        public delegate void ConnectionStatusEvent(CommLinkAbstract.ConnectionStates eState);

        /// <summary>
        /// Event fired when the connection status changes.
        /// </summary>
        public event ConnectionStatusEvent OnConnectionStatusEvent;
        #endregion

        /// <summary>
        /// Closes up anyform of open comm channels
        /// </summary>
        public void CommClose()
        {
            commObject.closeConnection();
        }
    }
}
